﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using PSLib.CodeFormating.Lexems;
using PSLib.CodeFormating.Adapters;

namespace LuaEditor2
{
	public static class Extention
	{
		#region TextPointer extension

		public static TextPointer GoToPoint(this TextPointer start, int offset)
		{
			if (offset == 0) return start;
			TextPointer end = start;
			TextPointer prevEnd = null;
			int jumpInterval = offset;
			while (true)
			{
				prevEnd = end;
				end = end.GetPositionAtOffset(jumpInterval, LogicalDirection.Forward);
				if (end == null)
				{
					jumpInterval /= 2;
					end = prevEnd;
					continue;
				}

				TextRange range = new TextRange(start, end);
				int textLenght = range.Text.Length;
				if (textLenght == offset)
				{
					TextPointerContext ctx = end.GetPointerContext(LogicalDirection.Backward);
					while (ctx != TextPointerContext.Text && ctx != TextPointerContext.None)
					{
						end = end.GetNextContextPosition(LogicalDirection.Backward);
						ctx = end.GetPointerContext(LogicalDirection.Backward);
					}
					return end;
				}

				if (textLenght > offset)
				{
					float coef = (float)offset / textLenght;
					jumpInterval = (int)((float)jumpInterval * coef);
					end = prevEnd;
				}
			}
		}

		#endregion

		#region TextElement extension

		// Tests to see if the specified TextElement is empty (has no printatble content).
		public static bool IsElementEmpty(this TextElement element)
		{
			// Find starting and ending insertion positions in the element.
			// Inward-facing directions are used to make sure that insertion position
			// will be found correctly in case when the element may contain inline 
			// formatting elements (such as a Span or Run that contains Bold or Italic elements).
			TextPointer start = element.ContentStart.GetInsertionPosition(LogicalDirection.Forward);
			TextPointer end = element.ContentEnd.GetInsertionPosition(LogicalDirection.Backward);

			// The element has no printable content if its first and last insertion positions are equal.
			return start.CompareTo(end) == 0;

		} // End IsEmptyElement method.

		#endregion

		#region Paragraph extension

		public class FormatLexemDelegateArgs : EventArgs
		{
			public FormatLexemDelegateArgs(Lexem lexem)
			{
				this.Lexem = lexem;
			}
			public Lexem Lexem { get; private set; }
			public Inline Inline { get; set; }
		}

		public static void LuaReFormat(this Paragraph paragraph, TextPointer curSelection, LanguageAdapter adapter, EventHandler<FormatLexemDelegateArgs> formatLexemDelegate)
		{
			TextRange textRange = new TextRange(paragraph.ElementStart, paragraph.ElementEnd);
			string blockText = textRange.Text;


			Run selectionRun = curSelection.Parent as Run;
			if (textRange.Contains(curSelection) && selectionRun != null && selectionRun.Tag is Lexem)
			{
				Lexem existingLexem = selectionRun.Tag as Lexem;
				Run prevInline = selectionRun.GetPrevInline() as Run;
				bool skipOptimization = false;
				if(prevInline != null)
				{
					Lexem prevLexem = prevInline.Tag as Lexem;
					if (prevLexem != null && prevLexem.Type == existingLexem.Type) skipOptimization = true;
				}

				LexemList list = LexemParser.Parse(selectionRun.Text, adapter,true);
				//Remove breakline lexem
				if (list.Count == 2 && list[0].Type == existingLexem.Type && !skipOptimization)
				{
					selectionRun.Tag = list[0];
					FormatLexemDelegateArgs args = new FormatLexemDelegateArgs(list[0]);
					args.Inline = selectionRun;
					if (formatLexemDelegate != null) formatLexemDelegate(paragraph, args);
					return;
				}
			}

			//Modify paragraph
			//Clean block source
			if (blockText.EndsWith("\r\n")) blockText = blockText.TrimEnd('\r', '\n');

			//Parse source
			LexemList lList = LexemParser.Parse(blockText, adapter,true);
			//Remove breakline lexem
			if (lList.Count > 0) lList.RemoveAt(lList.Count - 1);

			List<Inline> newInlineList = new List<Inline>();
			foreach (var item in lList)
			{
				FormatLexemDelegateArgs args = new FormatLexemDelegateArgs(item);
				if(formatLexemDelegate != null) formatLexemDelegate(paragraph, args);
				if (args.Inline != null) newInlineList.Add(args.Inline);
			}

			paragraph.Inlines.Clear();
			paragraph.Inlines.AddRange(newInlineList);
		}
		
		public static double GetWidth(this Paragraph par)
		{
			double width = 0;
			
			foreach (Run item in par.Inlines)
			{
				Typeface typeface = new Typeface(
					item.FontFamily,
					item.FontStyle,
					item.FontWeight,
					item.FontStretch);
				FormattedText ft = new FormattedText(
					item.Text,
					System.Globalization.CultureInfo.CurrentCulture,
					FlowDirection.LeftToRight,
					typeface,
					item.FontSize,Brushes.Black);
				ft.MaxLineCount = 1;
				width += ft.WidthIncludingTrailingWhitespace;
			}
			return width;
		}

		public static Paragraph GetNextParagraph(this Paragraph par,LogicalDirection dirrection)
		{
			Paragraph result = null;
			if(dirrection == LogicalDirection.Forward)
			{
				TextPointer pt = par.ElementEnd;
				pt = pt.GetNextContextPosition(LogicalDirection.Forward);
				if (pt != null) result = pt.Paragraph;
			}
			else
			{
				TextPointer pt = par.ElementStart;
				pt = pt.GetNextContextPosition(LogicalDirection.Backward);
				if (pt != null) result = pt.Paragraph;
			}

			return result;
		}

		#endregion

		#region FlowDocument extension

		public static Paragraph[] GetParagraphRange(this FlowDocument document, TextPointer start, TextPointer end)
		{
			List<Paragraph> result = new List<Paragraph>();
			Paragraph cur = null;
			while (true)
			{
				cur = start.Paragraph;
				if (cur == null) start = start.GetNextContextPosition(LogicalDirection.Forward);
				else break;

				if (start == null) break;
			}
			
			while (true)
			{
				bool match = false;
				if (cur != null)
				{
					match = match || (start.CompareTo(cur.ElementStart) >= 0 && start.CompareTo(cur.ElementEnd) <= 0);
					match = match || (end.CompareTo(cur.ElementStart) >= 0 && end.CompareTo(cur.ElementEnd) <= 0);
					match = match || (start.CompareTo(cur.ElementStart) <= 0 && end.CompareTo(cur.ElementEnd) >= 0);
				}
				if (match)
				{
					result.Add(cur);
					cur = cur.GetNextParagraph(LogicalDirection.Forward);
				}
				else break;
				
			}

			return result.ToArray();
		}

		#endregion

		#region Inline extension

		public static bool IsEqual(this Inline first, Inline second)
		{
			bool result = true;
			string firstText = new TextRange(first.ElementStart, first.ElementEnd).Text;
			string secondText = new TextRange(second.ElementStart, second.ElementEnd).Text;
			result = result && (firstText == secondText);
			result = result && (first.GetType() == second.GetType());
			result = result && (first.FontFamily == second.FontFamily);
			result = result && (first.FontSize == second.FontSize);
			result = result && (first.FontStretch == second.FontStretch);
			result = result && (first.FontStyle == second.FontStyle);
			result = result && (first.FontWeight == second.FontWeight);
			result = result && (first.Foreground == second.Foreground);
			
			Lexem firstLexem = first.Tag as Lexem;
			Lexem secondLexem = second.Tag as Lexem;
			if (firstLexem == null && secondLexem == null) result = true;
			else if (firstLexem == null && secondLexem != null) result = false;
			else if (firstLexem != null && secondLexem == null) result = false;
			else
			{
				result = result && (firstLexem.LexemSource == secondLexem.LexemSource);
				result = result && (firstLexem.Type == secondLexem.Type);
			}

			return result;
		}

		public static Inline GetPosition(this InlineCollection coll, Inline inline)
		{
			foreach (var item in coll)
			{
				if (item.IsEqual(inline)) return item;
			}
			return null;
		}

		public static Inline GetPrevInline(this Inline inline)
		{
			TextPointer ptCurInline = inline.ElementStart;
			TextPointer ptPrevInline = ptCurInline.GetNextContextPosition(LogicalDirection.Backward);
			if (ptCurInline.Paragraph == ptPrevInline.Paragraph && ptCurInline.Parent != ptPrevInline.Parent) return ptPrevInline.Parent as Inline;
			return null;
		}

		#endregion

		#region String extension

		public static string Utf8ToUnicode(this string utf8)
		{
			return Encoding.Unicode.GetString(
				Encoding.Convert(
				Encoding.UTF8,
				Encoding.Unicode,
				Encoding.UTF8.GetBytes(utf8)));
		}

		public static string UnicodeToUtf8(this string unicode)
		{
			return Encoding.UTF8.GetString(
				Encoding.Convert(
				Encoding.Unicode,
				Encoding.UTF8,
				Encoding.Unicode.GetBytes(unicode)));
		}

		#endregion
	}
}
